﻿package com.nicoB.tetris {
	import com.nicoB.tetris.events.TetrisEvent;	
	import com.nicoB.tetris.blocks.ABlock;
	import com.nicoB.tetris.blocks.LBlock;
	import com.nicoB.tetris.blocks.LineBlock;
	import com.nicoB.tetris.blocks.SBlock;
	import com.nicoB.tetris.blocks.SquareBlock;
	import com.nicoB.tetris.blocks.TBlock;
	import com.nicoB.tetris.constants.Blocks;
	
	import flash.events.EventDispatcher;	

	/**
	 * @author n.bush
	 * @project tetris
	 * @date 5 nov. 2008
	 * @desc 
	 */
	public class Engine extends EventDispatcher {

		private var _cells : Array;
		
		private var _currentBlock : ABlock;
		private var _posXBlock : int;
		private var _posYBlock : int;
		
		private var _lines : uint;
		private var _cols : uint;
		
		private var _colorBorders : uint = 0x333333;

		public function Engine(lines : uint, cols : uint){
			_lines = lines;
			_cols = cols;
			
			_buildBorders();	
		}
		
		private function _buildBorders():void{
			_cells = [];
			
			//on cree une ligne standard
			var line : Array = [];
			line.push(_colorBorders);
			for (var i : uint = 0; i < _cols - 2; i++) {
				line.push(0);
			}
			line.push(_colorBorders);
			
			//on duplique cette colonne et on la rajoute a chaque ligne
			for (var j : uint = 0; j < _lines - 1; j++) {
				_cells.push(line.concat());
			}
			
			//on rajoute le fond (une ligne de plein)
			line = [];
			for (var k : uint = 0; k < _cols; k++) {
				line.push(_colorBorders);
			}
			_cells.push(line.concat());
			
		}
		
		

		
		public function step():void {
			_posYBlock++;
			
			var e : TetrisEvent;
			
			//collision
			if (isCollision()){
				_posYBlock--;
				_cells = _mergeBlockWithCells();
				
				if (!_checkForLinesFull()){
					
					if ((_posYBlock - _currentBlock.height) < 0){
						e = new TetrisEvent(TetrisEvent.GAME_OVER);	
					}
					else {
						e = new TetrisEvent(TetrisEvent.BLOCK_POSITIONNED);
					}
					
					dispatchEvent(e);
				}
			}
		}

		
		//--------------------------------------
		//					LINES
		//--------------------------------------
		
		private function _checkForLinesFull():Boolean{
			
			var linesToRemove : Array = [];
			
			for (var i : uint = 0; i < _cells.length - 1; i++) {
				if (_isLineFull(i)){
					linesToRemove.push(i);
				}
			}
			
			if (linesToRemove.length > 0){
				var e : TetrisEvent = new TetrisEvent(TetrisEvent.LINE_FILLED);
				e.linesToRemove = linesToRemove;
				dispatchEvent(e);
				
				_currentBlock = null;
				
				return true;
			}
			
			
			return false;
		}
		
		//retourne si une ligne est pleine
		private function _isLineFull(line : uint):Boolean{
			for (var i : uint = 0; i < _cells[line].length; i++) {
				if (!_cells[line][i])
					return false;	
			}
			
			return true;
			
		}
		
		
		public function removeFullLines():void{
			
			for (var i : uint = 0; i < _cells.length - 1; i++) {
				if (_isLineFull(i)){
					_removeLine(i);
				}
			}
			
		}
		
		
		
		private function _removeLine(line : uint):void{
			
			//nouvelle ligne
			var newLine : Array = [];
			newLine.push(_colorBorders);
			for (var i : uint = 0; i < _cols - 2; i++) {
				newLine.push(0);
			}
			newLine.push(_colorBorders);
			
			
			_cells.splice(line, 1);
			
			_cells = [newLine].concat(_cells);
			
		}
		
		
		
		//--------------------------------------
		//					COLLISION
		//--------------------------------------
				
		private function isCollision():Boolean {
			for (var i : uint = 0;i < _currentBlock.cells.length; i++) {
				for (var j : uint = 0; j < _currentBlock.cells[i].length; j++) {
					if ((i + _posYBlock) >= 0 && (j + _posXBlock>=0) && _currentBlock.cells[i][j] && _cells[i + _posYBlock][j + _posXBlock]){
						return true;
					}
				}
			}
			
			return false;
		}
		
		
		//--------------------------------------
		//					GETTER
		//--------------------------------------
		
		public function get cells():Array{
			return _mergeBlockWithCells();
		}
		
		
		//--------------------------------------
		//					ADD BLOCK
		//--------------------------------------
		
		public function addBlock(type : String):void{
			
			switch (type){
				case Blocks.L:
					_currentBlock = new LBlock();
					break;
				case Blocks.S:
					_currentBlock = new SBlock();
					break;
				case Blocks.T:
					_currentBlock = new TBlock();
					break;
				case Blocks.LINE:
					_currentBlock = new LineBlock();
					break;
				case Blocks.SQUARE:
					_currentBlock = new SquareBlock();
					break;
			}

			_posXBlock = Math.floor((_cols / 2) - (_currentBlock.width / 2));
			_posYBlock = - _currentBlock.height;
		}
		
		
		//--------------------------------------
		//					MERGE
		//--------------------------------------
		
		private function _mergeBlockWithCells():Array{
			
			var mergedCells : Array = [];
			
			for (var i : uint = 0; i < _cells.length; i++) {
				var column : Array = [];
				for (var j : uint = 0; j < _cells[i].length; j++) {
					
					
					try {
						//si on est sur le block, on renvoie les valeurs mergées
						//sinon on renvoie tel quel
						if ((_currentBlock != null) && (i >= _posYBlock && i < (_posYBlock + _currentBlock.height)) && (j >= _posXBlock && j < (_posXBlock + _currentBlock.width))){
							column.push(_cells[i][j] || _currentBlock.cells[i-_posYBlock][j-_posXBlock]);
						}
						else {
							column.push(_cells[i][j]);
						}
					}
					catch (err : Error){
						trace("Error : ", this, "_mergeBlockWithCells", j-_posYBlock, i-_posYBlock);
					}
					
					
				}
				mergedCells.push(column);
			}
			
			return mergedCells;
		}
		
		
		//--------------------------------------
		//					ACTION BLOCK
		//--------------------------------------
		
		public function rotateBlock(way : uint):void {
			_currentBlock.rotate(way);
			
			if (isCollision())
				_currentBlock.rotate(-way);
			
			//on force un recalcule de la position
			moveBlockX(0);
		}
		
		
		//deplace le bloc
		public function moveBlockX(way : int):void{
			_posXBlock += way;
			
			//on empeche les collisions sur le cote
			if (isCollision())
				_posXBlock -= way;
		}
		
		
		public function moveDown():void{
			step();
		}
		
		
		//--------------------------------------
		//					CLEAR
		//--------------------------------------
		
		public function clear():void {
			_buildBorders();
		}
	}
}
